/*******************************************
 * Copyright 2007 Aphrodite Framework for javaME .
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * Email : owner@aphroditeme.dev.java.net
 * Contributors:
 *     Kent Elton - initial API and implementation
 *******************************************/

package com.aphrodite.io.comm;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Vector;

import javax.microedition.io.Connector;
import javax.microedition.io.SocketConnection;

import com.aphrodite.io.LineReader;

/**
 * FTP Connection API
 * <p>
 * Implements all of the RFC959 commands
 * </p>
 * 
 */
public final class FTPConnection extends CommConnection {
	public static final int RESP_ALL = 0xFF;

	private SocketConnection conn;
	private LineReader input;
	private int lastCode;
	private String lastMessage;
	private OutputStream output;

	/**
	 * Create an instance of Ftp2Me.
	 * 
	 * Connects to the given host, at port 21 User/pass/acct must be issued
	 * manually
	 * 
	 * @param host
	 *            Name of the host to connect to
	 * 
	 * @throws IOException
	 *             , FtpProtocolException
	 */
	public FTPConnection(String host) {
		this(host, "21");
	}

	/**
	 * Create an instance of Ftp2Me.
	 * 
	 * Connects to the given host and port User/pass/acct must be issued
	 * manually
	 * 
	 * @param host
	 *            Name of the host to connect to
	 * @param port
	 *            Port on the host to connect to
	 * 
	 */
	public FTPConnection(String host, String port) {
		this.serverAddr = host;
		this.serverPort = port;
	}

	/**
	 * Create an instance of Ftp2Me.
	 * 
	 * Issues the USER and PASS commands
	 * 
	 * @param host
	 *            Name of the host to connect to
	 * @param port
	 *            Port on the host to connect to
	 * @param user
	 *            Username to use for login
	 * @param pass
	 *            Password to use for login
	 */
	public FTPConnection(String host, String port, String user, String pass) {
		this(host, port);

	}

	/**
	 * Create an instance of Ftp2Me.
	 * 
	 * Issues the USER, PASS and ACCT commands
	 * 
	 * @param host
	 *            Name of the host to connect to
	 * @param port
	 *            Port on the host to connect to
	 * @param user
	 *            Username to use for login
	 * @param pass
	 *            Password to use for login
	 * @param acct
	 *            Account name to use with acct command
	 * 
	 * @throws IOException
	 *             , FtpProtocolException
	 */
	public FTPConnection(String host, String port, String user, String pass, String acct) throws IOException,
			FTPException {
		this(host, port, user, pass);
		acct(acct);
	}

	/**
	 * Abort
	 * 
	 * This command tells the server to abort the previous FTP service command
	 * and any associated transfer of data. The abort command may require
	 * "special action", as discussed in the RFC959 "Section on FTP Commands",
	 * to force recognition by the server. No action is to be taken if the
	 * previous command has been completed (including data transfer). The
	 * control connection is not to be closed by the server, but the data
	 * connection must be closed.
	 * 
	 * There are two cases for the server upon receipt of this command: (1) the
	 * FTP service command was already completed, or (2) the FTP service command
	 * is still in progress.
	 * 
	 * In the first case, the server closes the data connection (if it is open)
	 * and responds with a 226 reply, indicating that the abort command was
	 * successfully processed.
	 * 
	 * In the second case, the server aborts the FTP service in progress and
	 * closes the data connection, returning a 426 reply to indicate that the
	 * service request terminated abnormally. The server then sends a 226 reply,
	 * indicating that the abort command was successfully processed.
	 */
	public final boolean abor() throws IOException, FTPException {
		/*
		 * Some servers _really_ need to be frightened if we would like to say
		 * an ABOR while they sending data.
		 * 
		 * I don't know how to set the URG flag :(
		 */
		sendLine("\341\340\362ABOR");
		int resp = getResponse();
		if (resp == 426) {
			// Service request terminated
			expectResponse(226);
		} else if (resp != 226) {
			// There were no service request running, not given 226 => error.
			throw new FTPException("FTPException: " + lastMessage);
		}
		return false;
	}

	/**
	 * Account
	 * 
	 * The argument field is a Telnet string identifying the user’s account.
	 * The command is not necessarily related to the USER command, as some sites
	 * may require an account for login and others only for specific access,
	 * such as storing files. In the latter case the command may arrive at any
	 * time. There are reply codes to differentiate these cases for the
	 * automation: when account information is required for login, the response
	 * to a successful PASSword command is reply code 332. On the other hand, if
	 * account information is NOT required for login, the reply to a successful
	 * PASSword command is 230; and if the account information is needed for a
	 * command issued later in the dialogue, the server should return a 332 or
	 * 532 reply depending on whether it stores (pending receipt of the ACCounT
	 * command) or discards the command, respectively.
	 */
	public final void acct(String account) throws IOException, FTPException {
		sendLine("ACCT " + account);
		// TODO: What answer should I expect?
		expectResponse(200);
	}

	/**
	 * Allocate
	 * 
	 * This command may be required by some servers to reserve sufficient
	 * storage to accommodate the new file to be transferred. The argument shall
	 * be a decimal integer representing the number of bytes (using the logical
	 * byte size) of storage to be reserved for the file. For files sent with
	 * record or page structure a maximum record or page size (in logical bytes)
	 * might also be necessary; this is indicated by a decimal integer in a
	 * second argument field of the command. This second argument is optional,
	 * but when present should be separated from the first by the three Telnet
	 * characters <SP> R <SP>. This command shall be followed by a STORe or
	 * APPEnd command. The ALLO command should be treated as a NOOP (no
	 * operation) by those servers which do not require that the maximum size of
	 * the file be declared beforehand, and those servers interested in only the
	 * maximum record or page size should accept a dummy value in the first
	 * argument and ignore it.
	 */
	public final boolean allo(long size, long maxRecordSize) throws IOException, FTPException {
		if (maxRecordSize > 0) {
			sendLine("ALLO " + String.valueOf(size) + " R " + String.valueOf(maxRecordSize));
		} else {
			sendLine("ALLO " + String.valueOf(size));
		}
		// TODO: What answer should I expect?
		expectResponse(200);
		return false;
	}

	/**
	 * Append (with create)
	 * 
	 * This command causes the server-DTP to accept the data transferred via the
	 * data connection and to store the data in a file at the server site. If
	 * the file specified in the pathname exists at the server site, then the
	 * data shall be appended to that file; otherwise the file specified in the
	 * pathname shall be created at the server site.
	 */
	public final void appe(final InputStream is, String filename) throws IOException, FTPException {
		final PasvInfo pi = pasv();
		sendLine("APPE " + filename);
		new Thread() {
			public void run() {

				sendData(pi, is);

			}
		}.start();
	}

	/**
	 * Change to parent directory
	 * 
	 * This command is a special case of CWD, and is included to simplify the
	 * implementation of programs for transferring directory trees between
	 * operating systems having different syntaxes for naming the parent
	 * directory. The reply codes shall be identical to the reply codes of CWD.
	 * See Appendix II for further details.
	 */
	public final void cdup() throws IOException, FTPException {
		sendLine("CDUP");
		expectResponse(250);
	}

	/**
	 * Changes the working directory
	 * 
	 * This command allows the user to work with a different directory or
	 * dataset for file storage or retrieval without altering his login or
	 * accounting information. Transfer parameters are similarly unchanged. The
	 * argument is h1 pathname specifying a directory or other system dependent
	 * file group designator.
	 */
	public final void cwd(String dir) throws IOException, FTPException {
		sendLine("CWD " + dir);
		expectResponse(250);
	}

	/**
	 * Delete
	 * 
	 * This command causes the file specified in the pathname to be deleted at
	 * the server site. If an extra level of protection is desired (such as the
	 * query, "Do you really wish to delete?"), it should be provided by the
	 * user-FTP process.
	 */
	public final void dele(final String filename) throws IOException, FTPException {
		sendLine("DELE " + filename);
		expectResponse(250);
	}

	/**
	 * Disconnects from the FTP server.
	 * 
	 * Does NOT send the QUIT command, just closes the communication channels.
	 * It does not throw any exceptions or return error codes, it designed to be
	 * a "fire and forget" method.
	 */
	public final void disconnect() {
		try {
			input.close();
			output.close();
			conn.close();
		} catch (Exception e) {
			// There is no such thing as "Cannot close connection"
		} finally {
			input = null;
			output = null;
			conn = null;
		}
	}

	/**
	 * Gets a response. If the response code not equals with the given number,
	 * throws an FtpProtocolException
	 */
	public final void expectResponse(final int expectedCode) throws IOException, FTPException {
		if (getResponse() != expectedCode) {
			throw new FTPException("FTPException:" + lastMessage);
		}
	}

	/**
	 * Returns the last message from the server. Works for multi-line responses
	 * too
	 */
	public final String getLastMessage() {
		return lastMessage;
	}

	/**
	 * Gets response from server. Works with multi-line responses too.
	 */
	private final int getResponse() throws FTPException, IOException {
		StringBuffer sb = new StringBuffer();
		String line;
		sb.append((line = input.readLine()) + "\n");
		String strret = line.substring(0, 3);
		char multiLineIndicator = line.charAt(3);
		try {
			lastCode = Integer.parseInt(strret);
		} catch (NumberFormatException e) {
			throw new FTPException("FTPException: Bad response code");
		}
		if (multiLineIndicator == '-') {
			do {
				sb.append((line = input.readLine()) + "\n");
			} while (!line.substring(0, 3).equals(strret));
		} else if (multiLineIndicator != ' ') {
			throw new FTPException("FTPException: Malformed multi-line response");
		}
		lastMessage = sb.toString();
		return lastCode;
	}

	/**
	 * Help
	 * 
	 * This command shall cause the server to send helpful information regarding
	 * its implementation status over the control connection to the user. The
	 * command may take an argument (e.g., any command name) and return more
	 * specific information as a response. The reply is type 211 or 214. It is
	 * suggested that HELP be allowed before entering a USER command. The server
	 * may use this reply to specify site-dependent parameters, e.g., in
	 * response to HELP SITE.
	 */
	public final void help(final String command) throws IOException, FTPException {
		if (command != null) {
			sendLine("HELP " + command);
		} else {
			sendLine("HELP");
		}
		expectResponse(214);
	}

	/**
	 * Is the last message a negative permanent reply?
	 * 
	 * The command was not accepted and the requested action did not take place.
	 * The User-process is discouraged from repeating the exact request (in the
	 * same sequence). Even some "permanent" error conditions can be corrected,
	 * so the human user may want to direct his User-process to reinitiate the
	 * command sequence by direct action at some point in the future (e.g.,
	 * after the spelling has been changed, or the user has altered his
	 * directory status.)
	 */
	public final boolean isNegativePermanent() {
		return (lastCode >= 500 && lastCode < 600);
	}

	/**
	 * Is the last message a negative transient reply?
	 * 
	 * The command was not accepted and the requested action did not take place,
	 * but the error condition is temporary and the action may be requested
	 * again. The user should return to the beginning of the command sequence,
	 * if any. It is difficult to assign a meaning to "transient", particularly
	 * when two distinct sites (Server- and User-processes) have to agree on the
	 * interpretation. Each reply in the 4yz category might have a slightly
	 * different time value, but the intent is that the user-process is
	 * encouraged to try again. A rule of thumb in determining if a reply fits
	 * into the 4yz or the 5yz (Permanent Negative) category is that replies are
	 * 4yz if the commands can be repeated without any change in command form or
	 * in properties of the User or Server (e.g., the command is spelled the
	 * same with the same arguments used; the user does not change his file
	 * access or user name; the server does not put up a new implementation.)
	 */
	public final boolean isNegativeTransient() {
		return (lastCode >= 400 && lastCode < 500);
	}

	/**
	 * Is the last message a positive completion reply?
	 * 
	 * The requested action has been successfully completed. A new request may
	 * be initiated.
	 */
	public final boolean isPositiveCompletion() {
		return (lastCode >= 200 && lastCode < 300);
	}

	/**
	 * Is the last message a positive intermediate reply?
	 * 
	 * The command has been accepted, but the requested action is being held in
	 * abeyance, pending receipt of further information. The user should send
	 * another command specifying this information. This reply is used in
	 * command sequence groups.
	 */
	public final boolean isPositiveIntermediate() {
		return (lastCode >= 300 && lastCode < 400);
	}

	/**
	 * Is the last message a positive preliminary reply?
	 * 
	 * The requested action is being initiated; expect another reply before
	 * proceeding with a new command. (The user-process sending another command
	 * before the completion reply would be in violation of protocol; but
	 * server-FTP processes should queue any commands that arrive while a
	 * preceding command is in progress.) This type of reply can be used to
	 * indicate that the command was accepted and the user-process may now pay
	 * attention to the data connections, for implementations where simultaneous
	 * monitoring is difficult. The server-FTP process may send at most, one 1yz
	 * reply per command.
	 */
	public final boolean isPositivePreliminary() {
		return (lastCode >= 100 && lastCode < 200);
	}

	/**
	 * List
	 * 
	 * This command causes a list to be sent from the server to the passive DTP.
	 * If the pathname specifies a directory or other group of files, the server
	 * should transfer a list of files in the specified directory. If the
	 * pathname specifies h1 file then the server should send current
	 * information on the file. A null argument implies the user’s current
	 * working or default directory. The data transfer is over the data
	 * connection in type ASCII or type EBCDIC. (The user must ensure that the
	 * TYPE is appropriately ASCII or EBCDIC). Since the information on a file
	 * may vary widely from system to system, this information may be hard to
	 * use automatically in a program, but may be quite useful to a human user.
	 * 
	 * The file names and details are given as an enumeration of Strings.
	 */
	public final Enumeration list() throws IOException, FTPException {
		PasvInfo pi = pasv();

		sendLine("LIST");

		SocketConnection dataInput = (SocketConnection) Connector.open("socket://" + pi.ip + ":" + pi.port);

		expectResponse(150);

		Vector ret = new Vector();
		LineReader in = new LineReader(dataInput.openInputStream());

		String line;
		while (!(line = in.readLine()).equals("")) {
			ret.addElement(line);
		}
		in.close();
		dataInput.close();

		expectResponse(226);

		return ret.elements();
	}

	/**
	 * Login to the FTP connection
	 * 
	 * @param username
	 * @param password
	 * @throws IOException
	 * @throws FTPException
	 */
	public void login(String username, String password) throws IOException, FTPException {
		user(username);
		pass(password);
	}

	/**
	 * Make directory
	 * 
	 * This command causes the directory specified in the pathname to be created
	 * as a directory (if the pathname is absolute) or as a subdirectory of the
	 * current working directory (if the pathname is relative).
	 */
	public final void mkd(String pathname) throws IOException, FTPException {
		sendLine("MKD " + pathname);
		expectResponse(250);
	}

	/**
	 * Transfer mode
	 * 
	 * The argument is a single Telnet character code specifying the data
	 * transfer modes described in the Section on Transmission Modes. The
	 * following codes are assigned for transfer modes: S - Stream B - Block C -
	 * Compressed The default transfer mode is Stream.
	 */
	public final void mode(char mode) throws IOException, FTPException {
		sendLine("MODE " + mode);
		expectResponse(200);
	}

	/**
	 * Name list
	 * 
	 * This command causes a directory listing to be sent from server to user
	 * site. The pathname should specify h1 directory or other system-specific
	 * file group descriptor; h1 null argument implies the current directory.
	 * The server will return a stream of names of files and no other
	 * information. The data will be transferred in ASCII or EBCDIC type over
	 * the data connection as valid pathname strings separated by <CRLF> or
	 * <NL>
	 * . (Again the user must ensure that the TYPE is correct.) This command is
	 * intended to return information that can be used by a program to further
	 * process the files automatically. For example, in the implementation of a
	 * "multiple get" function.
	 * 
	 * File names returned as an enumeration of Strings.
	 */
	public final Enumeration nlst() throws IOException, FTPException {
		PasvInfo pi = pasv();

		sendLine("NLST");

		SocketConnection dataInput = (SocketConnection) Connector.open("socket://" + pi.ip + ":" + pi.port);

		expectResponse(150);

		Vector ret = new Vector();
		LineReader in = new LineReader(dataInput.openInputStream());

		String line;
		while (!(line = in.readLine()).equals("")) {
			ret.addElement(line);
		}
		in.close();
		dataInput.close();

		expectResponse(226);

		return ret.elements();
	}

	/**
	 * No operation
	 * 
	 * This command does not affect any parameters or previously entered
	 * commands. It specifies no action other than that the server send an OK
	 * reply.
	 */
	public final void noop() throws IOException, FTPException {
		sendLine("NOOP");
		expectResponse(200);
	}

	/**
	 * Password
	 * 
	 * The argument field is a Telnet string specifying the user’s password.
	 * This command must be immediately preceded by the user name command, and,
	 * for some sites, completes the user’s identification for access control.
	 * Since password information is quite sensitive, it is desirable in general
	 * to "mask" it or suppress typeout. It appears that the server has no
	 * foolproof way to achieve this. It is therefore the responsibility of the
	 * user-FTP process to hide the sensitive password information.
	 */
	public final void pass(String pass) throws IOException, FTPException {
		sendLine("PASS " + pass);
		expectResponse(230);
	}

	/**
	 * Passive
	 * 
	 * This command requests the server-DTP to "listen" on a data port (which is
	 * not its default data port) and to wait for h1 connection rather than
	 * initiate one upon receipt of h1 transfer command. The response to this
	 * command includes the host and port address this server is listening on.
	 */
	public final PasvInfo pasv() throws IOException, FTPException {
		PasvInfo pi = null;

		sendLine("PASV");
		expectResponse(227);

		int opening = lastMessage.indexOf('(');
		int closing = lastMessage.indexOf(')', opening + 1);
		if (closing > 0) {
			String dataLink = lastMessage.substring(opening + 1, closing);
			pi = new PasvInfo(dataLink);
		} else {
			throw new FTPException("FTPException: Badly formatted answer received after PASV");
		}

		return pi;
	}

	/**
	 * Data port
	 * 
	 * The argument is a HOST-PORT specification for the data port to be used in
	 * data connection. There are defaults for both the user and server data
	 * ports, and under normal circumstances this command and its reply are not
	 * needed. If this command is used, the argument is the concatenation of h1
	 * 32-bit internet host address and a 16-bit TCP port address. This address
	 * information is broken into 8-bit fields and the value of each field is
	 * transmitted as a decimal number (in character string representation). The
	 * fields are separated by commas. A port command would be: PORT
	 * h1,h2,h3,h4,p1,p2 where a is the high order 8 bits of the internet host
	 * address.
	 */
	public final void port(int h1, int h2, int h3, int h4, int port) throws IOException, FTPException {
		sendLine("PORT " + String.valueOf(h1) + "," + String.valueOf(h2) + "," + String.valueOf(h3) + ","
				+ String.valueOf(h4) + "," + String.valueOf((port & 0xFF00) >> 8) + "," + String.valueOf(port & 0xFF));
		expectResponse(200);
	}

	/**
	 * Print working directory
	 * 
	 * This command causes the name of the current working directory to be
	 * returned in the reply.
	 */
	public final String pwd() throws IOException, FTPException {
		sendLine("PWD");
		expectResponse(257);
		String dir = null;
		int firstQuote = lastMessage.indexOf('\"');
		int secondQuote = lastMessage.indexOf('\"', firstQuote + 1);
		if (secondQuote > 0) {
			dir = lastMessage.substring(firstQuote + 1, secondQuote);
		}
		return dir;
	}

	/**
	 * Quit
	 * 
	 * This command terminates a USER and if file transfer is not in progress,
	 * the server closes the control connection. If file transfer is in
	 * progress, the connection will remain open for result response and the
	 * server will then close it. If the user-process is transferring files for
	 * several USERs but does not wish to close and then reopen connections for
	 * each, then the REIN command should be used instead of QUIT.
	 * 
	 * An unexpected close on the control connection will cause the server to
	 * take the effective action of an abort (ABOR) and a logout (QUIT).
	 */
	public final void quit() throws IOException, FTPException {
		sendLine("QUIT");
	}

	/**
	 * Reinitialize
	 * 
	 * This command terminates a USER, flushing all I/O and account information,
	 * except to allow any transfer in progress to be completed. All parameters
	 * are reset to the default settings and the control connection is left
	 * open. This is identical to the state in which a user finds himself
	 * immediately after the control connection is opened. A USER command may be
	 * expected to follow.
	 */
	public final void rein() throws IOException, FTPException {
		sendLine("REIN");
		// What should follow?
		expectResponse(230);
	}

	/**
	 * Restart file transfer
	 * 
	 * The argument field represents the server marker at which file transfer is
	 * to be restarted. This command does not cause file transfer but skips over
	 * the file to the specified data checkpoint. This command shall be
	 * immediately followed by the appropriate FTP service command which shall
	 * cause file transfer to resume.
	 */
	public final void rest(String marker) throws IOException, FTPException {
		sendLine("REST " + marker);
		// What replies should I get?
		expectResponse(350);
	}

	/**
	 * Retrieve
	 * 
	 * This command causes the server-DTP to transfer a copy of the file,
	 * specified in the pathname, to the server- or user-DTP at the other end of
	 * the data connection. The status and contents of the file at the server
	 * site shall be unaffected.
	 */
	public final void retr(OutputStream outputStream, String filename) throws IOException, FTPException {
		PasvInfo pi = pasv();

		sendLine("RETR " + filename);

		SocketConnection dataInput = (SocketConnection) Connector.open("socket://" + pi.ip + ":" + pi.port);

		expectResponse(150);

		InputStream in = dataInput.openInputStream();

		byte[] buffer = new byte[128];
		int bytesRead = 0;
		while ((bytesRead = in.read(buffer)) != -1) {
			outputStream.write(buffer, 0, bytesRead);
		}
		in.close();
		outputStream.close();
		dataInput.close();

		expectResponse(226);
	}

	/**
	 * Remove directory
	 * 
	 * This command causes the directory specified in the pathname to be removed
	 * as a directory (if the pathname is absolute) or as a subdirectory of the
	 * current working directory (if the pathname is relative).
	 */
	public final void rmd(String pathname) throws IOException, FTPException {
		sendLine("RMD " + pathname);
		expectResponse(250);
	}

	/**
	 * Rename from
	 * 
	 * This command specifies the old pathname of the file which is to be
	 * renamed. This command must be immediately followed by a "rename to"
	 * command specifying the new file pathname.
	 */
	public final void rnfr(String pathname) throws IOException, FTPException {
		sendLine("RNFR " + pathname);
		expectResponse(350);
	}

	/**
	 * Rename to
	 * 
	 * This command specifies the new pathname of the file specified in the
	 * immediately preceding "rename from" command. Together the two commands
	 * cause a file to be renamed.
	 */
	public final void rnto(String pathname) throws IOException, FTPException {
		sendLine("RNTO " + pathname);
		expectResponse(250);
	}

	public void run() {
		try {
			conn = (SocketConnection) Connector.open("socket://" + serverAddr + ":" + serverPort);

			input = new LineReader(conn.openInputStream());
			output = conn.openOutputStream();
			isConnected = true;
			getResponse();
			// "Expected delay"
			if (lastCode == 120) {
				// Should we use timeout or something?
				expectResponse(220);
			} else if (lastCode != 220) {
				throw new FTPException(lastMessage);
			}
		} catch (IOException e) {
			System.out.println(e);
			isConnected = false;
		} catch (FTPException e) {
			System.out.println(e);
			isConnected = false;
		}
	}

	private void sendData(PasvInfo pi, InputStream is) {
		SocketConnection dataConnection;
		try {
			dataConnection = (SocketConnection) Connector.open("socket://" + pi.ip + ":" + pi.port);

			expectResponse(150);
			OutputStream dataOutput = dataConnection.openOutputStream();
			byte[] buffer = new byte[128];
			int bytesRead = 0;
			while ((bytesRead = is.read(buffer)) != -1) {
				dataOutput.write(buffer, 0, bytesRead);
			}
			dataOutput.close();
			is.close();
			dataConnection.close();

			expectResponse(226);

		} catch (IOException e) {
			System.out.println(e);
		} catch (FTPException e) {
			System.out.println(e);
		}
	}

	/**
	 * Sends a raw command to the FTP server.
	 */
	private final void sendLine(final String line) throws IOException, FTPException {
		if (conn == null) {
			throw new FTPException("FTPException: not connected.");
		}
		try {
			output.write((line + "\r\n").getBytes());
			output.flush();
		} catch (IOException e) {
			input = null;
			output = null;
			conn = null;
			throw e;
		}
	}

	/**
	 * Site parameters
	 * 
	 * This command is used by the server to provide services specific to his
	 * system that are essential to file transfer but not sufficiently universal
	 * to be included as commands in the protocol. The nature of these services
	 * and the specification of their syntax can be stated in a reply to the
	 * HELP SITE command.
	 */
	public final void site() throws IOException, FTPException {
		// Any parameters?
		sendLine("SITE");
		// What should this be?
		expectResponse(200);
	}

	/**
	 * Structure mount
	 * 
	 * This command allows the user to mount a different file system data
	 * structure without altering his login or accounting information. Transfer
	 * parameters are similarly unchanged. The argument is a pathname specifying
	 * h1 directory or other system dependent file group designator.
	 */
	public final void smnt(String pathname) throws IOException, FTPException {
		sendLine("SMNT " + pathname);
		// I guess it is a "file operation"...
		expectResponse(250);
	}

	/**
	 * Status
	 * 
	 * This command shall cause a status response to be sent over the control
	 * connection in the form of a reply. The command may be sent during a file
	 * transfer (along with the Telnet IP and Synch signals--see the Section on
	 * FTP Commands) in which case the server will respond with the status of
	 * the operation in progress, or it may be sent between file transfers. In
	 * the latter case, the command may have an argument field. If the argument
	 * is ha1 pathname, the command is analogous to the "list" command except
	 * that data shall be transferred over the control connection. If a partial
	 * pathname is given, the server may respond with a list of file names or
	 * attributes associated with that specification. If no argument is given,
	 * the server should return general status information about the server FTP
	 * process. This should include current values of all transfer parameters
	 * and the status of connections.
	 */
	public final void stat(String pathname) throws IOException, FTPException {
		if (pathname != null) {
			sendLine("STAT " + pathname);
		} else {
			sendLine("STAT");
		}
		getResponse();
		if (!isPositiveCompletion()) {
			throw new FTPException(lastMessage);
		}
	}

	/**
	 * Store
	 * 
	 * This command causes the server-DTP to accept the data transferred via the
	 * data connection and to store the data as a file at the server site. If
	 * the file specified in the pathname exists at the server site, then its
	 * contents shall be replaced by the data being transferred. A new file is
	 * created at the server site if the file specified in the pathname does not
	 * already exist.
	 */
	public final void stor(InputStream is, String filename) throws IOException, FTPException {
		PasvInfo pi = pasv();

		sendLine("STOR " + filename);

		SocketConnection dataConnection = (SocketConnection) Connector.open("socket://" + pi.ip + ":" + pi.port);

		expectResponse(150);

		OutputStream dataOutput = dataConnection.openOutputStream();
		byte[] buffer = new byte[128];
		int bytesRead = 0;
		while ((bytesRead = is.read(buffer)) != -1) {
			dataOutput.write(buffer, 0, bytesRead);
		}
		dataOutput.close();
		is.close();
		dataConnection.close();

		expectResponse(226);
	}

	/**
	 * Store unique
	 * 
	 * This command behaves like STOR except that the resultant file is to be
	 * created in the current directory under a name unique to that directory.
	 * The 250 Transfer Started response must include the name generated.
	 */
	public final void stou(InputStream is, String filename) throws IOException, FTPException {
		PasvInfo pi = pasv();

		sendLine("STOR " + filename);

		SocketConnection dataConnection = (SocketConnection) Connector.open("socket://" + pi.ip + ":" + pi.port);

		expectResponse(150);

		OutputStream dataOutput = dataConnection.openOutputStream();
		byte[] buffer = new byte[128];
		int bytesRead = 0;
		while ((bytesRead = is.read(buffer)) != -1) {
			dataOutput.write(buffer, 0, bytesRead);
		}
		dataOutput.close();
		is.close();
		dataConnection.close();

		expectResponse(226);
	}

	/**
	 * File structure
	 * 
	 * The argument is a single Telnet character code specifying file structure
	 * described in the Section on Data Representation and Storage. The
	 * following codes are assigned for structure: F - File (no record
	 * structure) R - Record structure P - Page structure The default structure
	 * is File.
	 */
	public final void stru(char structure) throws IOException, FTPException {
		sendLine("STRU " + structure);
		expectResponse(200);
	}

	/**
	 * System
	 * 
	 * This command is used to find out the type of operating system at the
	 * server. The reply shall have as its first word one of the system names
	 * listed in the current version of the Assigned Numbers document (Reynolds,
	 * Joyce, and Jon Postel, "Assigned Numbers", RFC 943, ISI, April 1985).
	 */
	public final void syst() throws IOException, FTPException {
		sendLine("SYST");
		expectResponse(215);
	}

	/**
	 * Calls type(type, 8) ( see type(char type, int bytesize) )
	 */
	public final void type(char type) throws IOException, FTPException {
		type(type, 8);
	}

	/**
	 * Set transfer type. Valid types are:
	 * 
	 * A(SCII) E(BCDIC) I(MAGE) L(OCAL < byte size >) N(on-print) T(elnet format
	 * effector) C(arriage Control)
	 */
	public final void type(char type, int bytesize) throws IOException, FTPException {
		if (type == 'L') {
			sendLine("TYPE " + type + " " + String.valueOf(bytesize));
		} else {
			sendLine("TYPE " + type);
		}
		expectResponse(200);
	}

	/**
	 * User name
	 * 
	 * The argument field is a Telnet string identifying the user. The user
	 * identification is that which is required by the server for access to its
	 * file system. This command will normally be the first command transmitted
	 * by the user after the control connections are made (some servers may
	 * require this). Additional identification information in the form of a
	 * password and/or an account command may also be required by some servers.
	 * Servers may allow a new USER command to be entered at any point in order
	 * to change the access control and/or accounting information. This has the
	 * effect of flushing any user, password, and account information already
	 * supplied and beginning the login sequence again. All transfer parameters
	 * are unchanged and any file transfer in progress is completed under the
	 * old access control parameters.
	 */
	public final void user(final String user) throws IOException, FTPException {
		sendLine("USER " + user);
		expectResponse(331);
	}

}
